/************************************************************************
* parse.c
* Copyright (C) Lisa Milne 2013 <lisa@ltmnet.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
************************************************************************/

#include "dish.h"
#include "env.h"
#include "path.h"

static char tty_escapes[8][4] = {
	{27,'[','0','K'},
	{27,'[','1','K'},
	{27,'[','2','K'},
	{27,'[','1','C'},
	{27,'[','1','D'},
	{27,']','2',';'},
	{27,'7'},
	{27,'8'},
};

enum {
	CLEARRIGHT,
	CLEARLEFT,
	CLEARLINE,
	MOVERIGHT,
	MOVELEFT,
	SETTITLE,
	SAVECURSOR,
	RESTORECURSOR
};

static int prompt(int out)
{
	char ps[CFG_INPUT_BUFFER];
	int l;
	char* n = env_get("nick");
	char* h = env_get("HOSTNAME");
	char* p = env_get("PWD");

	l = snprintf(ps,CFG_INPUT_BUFFER,"%s@%s [%s]",n,h,p);
	write(out,tty_escapes[SETTITLE],4);
	write(out,ps,l);
	write(out,"\7",1);

	if (!getuid()) {
		strcat(ps," # ");
	}else{
		strcat(ps," $ ");
	}

	l = strlen(ps);
	write(out,ps,l);

	return l;
}

static void cmd_free(cmd_t *c)
{
	if (c->next)
		cmd_free(c->next);

	clist_free(c->argv);
	clist_free(c->env);

	if (c->stdin.name) {
		free(c->stdin.name);
		close(c->stdin.fd);
	}

	if (c->stdout.name) {
		free(c->stdout.name);
		close(c->stdout.fd);
	}

	if (c->stderr.name) {
		free(c->stderr.name);
		close(c->stderr.fd);
	}

	free(c);
}

static cmd_t *cmd_create(cmd_t *p, int pp, int in, int out, int err)
{
	cmd_t *c = malloc(sizeof(cmd_t));

	c->prev = p;
	c->next = NULL;

	if (p)
		p->next = c;

	c->flags = CMD_WAIT;
	c->estate = 0;
	c->pid = 0;

	c->cmd = NULL;
	c->argc = 0;
	c->argv = clist_gen(NULL);
	c->env = env_toclist();

	c->stdin.fd = in;
	c->stdin.name = NULL;

	c->stdout.fd = out;
	c->stdout.name = NULL;

	c->stderr.fd = err;
	c->stderr.name = NULL;

	if (p && pp) {
		int fds[2];
		if (pipe(fds)) {
			ERROR("error: Failed to create pipe.\n");
			cmd_free(c);
			return NULL;
		}
		c->stdin.name = strdup("");
		c->stdin.fd = fds[0];

		p->stdout.name = strdup("");
		p->stdout.fd = fds[1];
	}

	return c;
}

static cmd_t *parse_to_cmd(char* line, int in, int out, int err)
{
	cmd_t *cmd;
	cmd_t *c;
	int b = 1;
	int cnt;
	int i;
	char** t = tokenize(line,&cnt);

	cmd = cmd_create(NULL,0,in,out,err);
	c = cmd;

	for (i=0; i<cnt; i++) {
		if (b && strchr(t[i],'=')) {
			c->env = clist_append(c->env,t[i],NULL);
			if (t[i+1])
				continue;
			env_set(t[i],NULL,0);
			cmd_free(cmd);
			return NULL;
		}
		if (!strcmp(t[i],"<")) {
			i++;
			if (!t[i]) {
				cmd_free(cmd);
				ERROR("error: no input file specified for redirect.\n");
				return NULL;
			}
			c->stdin.name = strdup(t[i]);
			errno = 0;
			c->stdin.fd = open(c->stdin.name,O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP);
			if (c->stdin.fd < 0) {
				if (error(c->stdin.name))
					ERROR("error: could not open stdin redirect.\n");
				cmd_free(cmd);
				return NULL;
			}
			continue;
		}
		if (t[i][0] == '>' && (!t[i][1] || t[i][1] == '>')) {
			int a = t[i][1];
			if (!strcmp(t[i-1],"2")) {
				int l = clist_count(c->argv);
				if (l) {
					l--;
					free(c->argv[l]);
					c->argv[l] = NULL;
				}
				i++;
				if (!t[i]) {
					cmd_free(cmd);
					ERROR("error: no output file specified for redirect.\n");
					return NULL;
				}
				if (!strcmp(t[i],"&1")) {
					c->stderr.fd = dup(c->stdout.fd);
				}else{
					c->stderr.name = strdup(t[i]);
					errno = 0;
					if (a) {
						c->stderr.fd = open(c->stderr.name,O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP);
					}else{
						c->stderr.fd = open(c->stderr.name,O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP);
					}
					if (c->stderr.fd < 0) {
						if (error(c->stderr.name))
							ERROR("error: could not open stderr redirect.\n");
						cmd_free(cmd);
						return NULL;
					}
				}
			}else{
				i++;
				if (!t[i]) {
					cmd_free(cmd);
					ERROR("error: no output file specified for redirect.\n");
					return NULL;
				}
				if (!strcmp(t[i],"&2")) {
					c->stdout.fd = dup(c->stderr.fd);
				}else{
					c->stdout.name = strdup(t[i]);
					errno = 0;
					if (a) {
						c->stdout.fd = open(c->stdout.name,O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP);
					}else{
						c->stdout.fd = open(c->stdout.name,O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP);
					}
					if (c->stdout.fd < 0) {
						if (error(c->stdout.name))
							ERROR("error: could not open stdout redirect.\n");
						cmd_free(cmd);
						return NULL;
					}
				}
			}
			continue;
		}
		if (!strcmp(t[i],"|")) {
			c->argc = clist_count(c->argv);
			c = cmd_create(c,1,in,out,err);
			c->prev->flags = CMD_IMMEDIATE;
			continue;
		}
		if (!strcmp(t[i],";")) {
			c->argc = clist_count(c->argv);
			c = cmd_create(c,0,in,out,err);
			c->prev->flags = CMD_WAIT;
			continue;
		}
		if (!strcmp(t[i],"&&")) {
			c->argc = clist_count(c->argv);
			c = cmd_create(c,0,in,out,err);
			c->prev->flags = CMD_CONDITIONAL;
			continue;
		}
		if (!strcmp(t[i],"||")) {
			c->argc = clist_count(c->argv);
			c = cmd_create(c,0,in,out,err);
			c->prev->flags = CMD_CONDITIONAL | CMD_INVERSE;
			continue;
		}
		if (!strcmp(t[i],"&")) {
			c->flags |= CMD_BACKGROUND;
			continue;
		}
		c->argv = clist_append(c->argv,t[i],NULL);
		if (!c->cmd) {
			c->cmd = c->argv[0];
			b = 0;
		}
	}

	c->argc = clist_count(c->argv);
	clist_free(t);

	return cmd;
}

/* parse a single command line */
int parse_line(char* line, int in, int out, int err)
{
	cmd_t *cmd;
	cmd_t *c;
	int r = 0;

	if (!line[0])
		return 0;

	cmd = parse_to_cmd(line,in,out,err);
	if (!cmd)
		return 0;
	c = cmd;

	while (c) {
		if (cmd->cmd) {
			r = forkit(c);
			if (r)
				break;
		}
		c = c->next;
	}

	cmd_free(cmd);

	return r;
}

/* parsea single command line, sending stdout to a buffer */
int parse_line_buff(char* line, int in, char* out, int outl, int err)
{
	cmd_t *cmd;
	cmd_t *c;
	int r = 0;
	int fd[2];
	int i;

	if (!line[0]) {
		out[0] = 0;
		write(err,"error: no command supplied.\n",28);
		return 0;
	}

	if (pipe(fd)) {
		out[0] = 0;
		write(err,"error: could not open pipe.\n",28);
		return 0;
	}

	cmd = parse_to_cmd(line,in,fd[1],err);
	out[0] = 0;
	if (!cmd) {
		write(err,"error: unparsable command.\n",27);
		return 0;
	}
	c = cmd;

	while (c) {
		if (cmd->cmd) {
			r = forkit(c);
			if (r)
				break;
		}
		c = c->next;
	}

	cmd_free(cmd);

	outl--;
	close(fd[1]);
	i = read(fd[0],out,outl);
	if (i<0)
		i = 0;

	out[i] = 0;

	close(fd[0]);

	return r;
}

/* parse commands from stream */
int parse_stream(int in, int out, int err)
{
	char buff[CFG_INPUT_BUFFER];
	char line[CFG_INPUT_BUFFER];
	int bs = 0;
	int be = 0;
	int le = 0;
	int r = 0;
	int e = 0;

	while (!r) {
		if (be == bs) {
			errno = 0;
			be = read(in,buff,CFG_INPUT_BUFFER);
			if (be < 1 || errno) {
				write(out,"\n",1);
				break;
			}
			bs = 0;
		}
		if (e) {
			if (buff[bs] == '\n')
				e = 0;
			bs++;
			continue;
		}
		if (le >= CFG_INPUT_BUFFER) {
			ERROR("error: command exceeds input buffer.\n");
			le = 0;
			e = 1;
			continue;
		}
		if (buff[bs] == '\r')
			continue;
		if (buff[bs] == '\n') {
			if (le && line[le-1] == '\\') {
				le--;
				continue;
			}
			line[le] = 0;
			r = parse_line(line,in,out,err);
			if (r<0)
				break;
			le = 0;
			bs++;
			continue;
		}else{
			line[le++] = buff[bs++];
		}
	}

	if (r<0)
		r = 0;

	return r;
}

/* parse the interactve command line */
int parse_interactive(int in, int out, int err)
{
	char buff[CFG_INPUT_BUFFER];
	char buffa[CFG_INPUT_BUFFER];
	char c;
	int o = 0;
	char is = 0;
	char* b;
	int r = 0;
	int l;

	buffa[0] = 0;

	tabfill_clear();
	history_load();
	set_tty_raw();
	prompt(out);
	fsync(out);

	/* keep going till EOT (ctrl+d) */
	while ((c = getch(in))) {
		if (c == 0x04) {
			if (o || buffa[0])
				continue;
			break;
		}
		if (c == is && (!o || buff[o-1] != '\\')) {
			is = 0;
		}else if (c == '\'' || c == '"') {
			is = c;
		}
		/* backspace */
		if (c == 0x7F) {
			if (!o)
				continue;
			o--;
			write(out,tty_escapes[MOVELEFT],4);
			if (buffa[0]) {
				l = strlen(buffa);
				write(out,buffa,l);
				write(out," ",1);
				write(out,tty_escapes[MOVELEFT],4);
				for (;l;l--) {
					write(out,tty_escapes[MOVELEFT],4);
				}
			}else{
				write(out," ",1);
				write(out,tty_escapes[MOVELEFT],4);
			}
			if (buff[o] == is)
				is = 0;
			buff[o] = 0;
			fsync(out);
			continue;
		/* ctrl+c */
		}else if (c == 0x03) {
			write(out,"\r\n",2);
			prompt(out);
			fsync(out);
			is = 0;
			o = 0;
			buffa[0] = 0;
			continue;
		/* delete */
		}else if (c == 0x08) {
			if (!buff[0])
				continue;
			strcpy(buffa,buffa+1);
			write(out,tty_escapes[CLEARRIGHT],4);
			write(out,tty_escapes[SAVECURSOR],2);
			l = strlen(buffa);
			write(out,buffa,l);
			write(out,tty_escapes[RESTORECURSOR],2);
			fsync(out);
			continue;
		}
		if (!is) {
			if (c > 64 && c < 69 && o > 1 && buff[o-2] == 27 && buff[o-1] == 91) {
				o -= 2;
				buff[o] = 0;
				/* up */
				if (c == 65) {
					b = history_prev();
					write(out,tty_escapes[CLEARLINE],4);
					write(out,"\r",1);
					prompt(out);
					strcpy(buff,b);
					o = strlen(buff);
					write(out,buff,o);
					buffa[0] = 0;
					fsync(out);
					continue;
				/* down */
				}else if (c == 66) {
					b = history_next();
					write(out,tty_escapes[CLEARLINE],4);
					write(out,"\r",1);
					prompt(out);
					strcpy(buff,b);
					o = strlen(buff);
					write(out,buff,o);
					buffa[0] = 0;
					fsync(out);
					continue;
				/* right */
				}else if (c == 67) {
					if (!buffa[0])
						continue;
					strcat(buff,buffa);
					o++;
					if (buff[o] == is)
						is = 0;
					strcpy(buffa,buff+o);
					buff[o] = 0;
					write(out,tty_escapes[CLEARLINE],4);
					write(out,"\r",1);
					prompt(out);
					write(out,buff,o);
					l = strlen(buffa);
					write(out,buffa,l);
					for (;l;l--) {
						write(out,tty_escapes[MOVELEFT],4);
					}
					fsync(out);
					continue;
				/* left */
				}else if (c == 68) {
					if (!o)
						continue;
					strcat(buff,buffa);
					o--;
					if (buff[o] == is)
						is = 0;
					strcpy(buffa,buff+o);
					buff[o] = 0;
					write(out,tty_escapes[CLEARLINE],4);
					write(out,"\r",1);
					prompt(out);
					write(out,buff,o);
					l = strlen(buffa);
					write(out,buffa,l);
					for (;l;l--) {
						write(out,tty_escapes[MOVELEFT],4);
					}
					fsync(out);
					continue;
				}
			}else if (c == '\n' || c == '\r') {
				write(out,"\r\n",2);
				if (o) {
					set_tty_cooked();
					strcat(buff,buffa);
					r = parse_line(buff,in,out,err);
					if (r<0)
						break;
					tabfill_clear();
					set_tty_raw();
					history_add(buff);
				}
				prompt(out);
				fsync(out);
				o = 0;
				buff[o] = 0;
				buffa[0] = 0;
				continue;
			}else if (c == '\t') {
				b = tabfill_get(buff);
				if (!b)
					continue;
				o = strlen(buff);
				write(out,tty_escapes[CLEARLINE],4);
				write(out,"\r",1);
				prompt(out);
				write(out,buff,o);
				l = strlen(buffa);
				write(out,buffa,l);
				for (;l;l--) {
					write(out,tty_escapes[MOVELEFT],4);
				}
				fsync(out);
				continue;
			}
		}
		if (o < CFG_INPUT_BUFFER) {
			write(out,&c,1);
			if (buffa[0]) {
				l = strlen(buffa);
				write(out,buffa,l);
				for (;l;l--) {
					write(out,tty_escapes[MOVELEFT],4);
				}
			}
			fsync(out);
			buff[o++] = c;
			buff[o] = 0;
		}
	}
	set_tty_cooked();
	history_save();
	write(out,"\n",1);
	return r;
}
